package com.keter.common.security;

import com.alibaba.fastjson.JSONObject;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Maps;
import com.jfinal.kit.Base64Kit;
import com.keter.common.domain.user.User;
import com.keter.common.domain.user.UserController;
import com.keter.framework.core.exception.ValidateException;
import com.keter.framework.web.result.JSONResult;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.Serializable;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * 处理用户身份认证相关的服务
 */
@Service
public class Security implements Serializable {

    private static final Logger logger = LoggerFactory.getLogger(Security.class);
    private static final long serialVersionUID = -3301605591108950415L;

    private static final String CLAIM_KEY_USERNAME = "sub";
    private static final String CLAIM_KEY_USERID = "id";
    private static final String CLAIM_KEY_EMAIL = "email";
    private static final String CLAIM_KEY_CREATED = "created";
    private static final String CLAIM_KEY_ROLES = "roles";

    @Value("${jwt.expiration:3600}")
    private Long expiration;

    @Autowired
    UserController userController;

    public JSONResult findUserByUsername(String username) {
        // 领域服务之间互相调用
        JSONResult user =  userController.findByUsername(username);
        // 处理登录认证相关逻辑
        if(user.isEmpty()){
            throw new ValidateException("用户不存在！");
        }
        if(UserStatus.STATUS_LOCKED.value().equals(user.object().getString("status"))){
            throw new ValidateException("用户锁定!");
        }
        if(UserStatus.STATUS_DISABLED.value().equals(user.object().getString("status"))){
            throw new ValidateException("用户禁用!");
        }
        return user;
    }

    public String getUsername(String token) {
        String username = null;
        try {
            final Claims claims = getClaims(token);
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    public User getUser(String token) {
        final Claims claims = getClaims(token);
        if (claims == null) {
            return null;
        }
        User user = new User();
        user.put("id", claims.get(CLAIM_KEY_USERID).toString());
        user.put("username", claims.getSubject());
        user.put("authorities", claims.get(CLAIM_KEY_ROLES));
        user.put("email", claims.get(CLAIM_KEY_EMAIL));
        return user;
    }

    public String refresh(String token) {
        final Claims claims = getClaims(token);
        claims.put(CLAIM_KEY_CREATED, new Date());
        return generate(claims);
    }

    public String regenerate(String token,UserController user) {
        String username = (String) getClaims(token).get(CLAIM_KEY_USERNAME);
        return generate(user.findByUsername(username).object());
    }

    public String generate(JSONObject userDetails) {
        Map<String, Object> claims = Maps.newHashMap();
        claims.put(CLAIM_KEY_USERID, userDetails.getString("id"));
        claims.put(CLAIM_KEY_USERNAME, userDetails.getString("username"));
        claims.put(CLAIM_KEY_ROLES, userDetails.get("authorities"));
        claims.put(CLAIM_KEY_EMAIL, userDetails.get("email"));
        claims.put(CLAIM_KEY_CREATED, new Date());
        return generate(claims);
    }

    private Claims getClaims(String token) {
        Claims claims;
        try {
            claims = Jwts.parser()
                    .setSigningKey(buildSecret(token))
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            logger.error("token解析失败:{}", e.getMessage());
            throw new ValidateException("非法token!");
        }
        return claims;
    }

    private String generate(Map<String, Object> claims) {
        return Jwts.builder()
                .setClaims(claims)
                .setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
                .signWith(SignatureAlgorithm.HS512, buildSecret(claims))
                .compact();
    }

    // TODO:如果用户修改了密码，需要有个机制清空对应的缓存！
    LoadingCache<String, String> secretCache = CacheBuilder.newBuilder()
            .maximumSize(1000)
            .expireAfterAccess(10, TimeUnit.MINUTES)
            .build(new CacheLoader<String, String>() {
                @Override
                public String load(String userName) {
                    logger.info("我被调用了");
                    // 这里是key根据实际去取值的方法
                    return makeSecret(userName);
                }
            });
    /**
     * 构造一种用户身份相关的动态secret
     * 可用于密码的变更、挂失等
     * @return
     */
    private String buildSecret(Map<String, Object> claims)  {
        try {
            return secretCache.get((String)claims.get(CLAIM_KEY_USERNAME));
        } catch (ExecutionException e) {
            logger.error("密钥获取失败!", e);
        }
        return null;
    }

    private String buildSecret(String token) throws ExecutionException {
//        return makeSecret(getUsernameFromToken(token));
        return secretCache.get(getUsernameFromToken(token));
    }

    private String makeSecret(String userName) {
        String pwd = userController.findByUsername(userName).string("password");
        //使用用户密码的后六位作为动态密钥
        return StringUtils.substring(pwd,pwd.length()-6);
    }

    /**
     * 从token中读取指定的属性内容
     * @param token
     * @return
     */
    private String getUsernameFromToken(String token) {
        String[] arr = StringUtils.split(token,".");
        String sub = Base64Kit.decodeToStr(arr[1]);
        return JSONObject.parseObject(sub).getString(CLAIM_KEY_USERNAME);
    }

    public enum UserStatus {

        /*正常*/
        STATUS_NORMAL(0),
        /*锁定*/
        STATUS_LOCKED(11),
        /*禁用*/
        STATUS_DISABLED(12);

        private int nCode;

        UserStatus(int _nCode) {
            this.nCode = _nCode;
        }

        public String value() {
            return String.valueOf(this.nCode);
        }

        @Override
        public String toString() {
            return value();
        }
    }

}

